#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <omp.h>

using namespace std;

typedef long long ll;

#define pb push_back
#define mp make_pair
#define S second
#define F first
#define INF 1000000000
#define MEMSET_INF 127
#define N 1000
#define N_THREADS 8

ll dist[N][N];
int e[(N*(N-1))/2][3]; // 0 -> vertice 1, 1 -> vertice 2, 2 -> peso
int indexes[N][N];
int sortAux;
vector< pair<ll, ll> > g[N];

// Funcao de comparacao que eh usada na ordenacao
bool comp(int i, int j){
	return dist[sortAux][i] < dist[sortAux][j];
}

// Dijkstra
void dij(int s){
	priority_queue< pair <ll, ll>, vector < pair <ll, ll> >, greater < pair <ll, ll > > >  pq;
	dist[s][s] = 0;
	pq.push(mp(0, s));

	while(!pq.empty()){
		pair< ll, ll> c = pq.top(); pq.pop();
		ll cdist = c.F;
		ll cnode = c.S;
		if(cdist > dist[s][cnode])
			continue;
		for (int i = 0; i < g[cnode].size(); ++i){
			ll adjn = g[cnode][i].F;
			ll adjdist = g[cnode][i].S;

			if(cdist + adjdist < dist[s][adjn]){
				dist[s][adjn] = cdist + adjdist;
				pq.push(mp(dist[s][adjn], adjn));
			}
		}
	}
}

int main(int argc, char const *argv[]){
	// Leitura dos dados
	int n, m;
	cin >> n >> m;

	// Leitura do grafo
	for(int i = 0; i < m; i++){
		int a, b, c;
		cin >> a >> b >> c;
		a--; b--;
		// multiplicar por 2 para evitar operacoes com floats
		g[a].pb(mp(b, 2*c)); 
		g[b].pb(mp(a, 2*c));
		e[i][0] = a;
		e[i][1] = b;
		e[i][2] = 2*c;
	}

	// Inicializar dist
	for(int i = 0; i < n; i++)
		for(int j = 0; j < n; j++)
			dist[i][j] = INF;
	

	// Dijkstra para todos os vertices
	#pragma omp parallel num_threads(N_THREADS)
	{
		#pragma omp for private(i)
		for (int i = 0; i < n; i++){
			dij(i);
		}
	}

	// Inicializa a matriz que ira conter os indices dos nos ordenados por distancia
	for(int i = 0; i < n; i++){
		for(int j = 0; j < n; j++){
			indexes[i][j] = j;
		}
		sortAux = i;
		sort(indexes[i], indexes[i] + n, comp);
	}


	// Obter melhor resposta para hospitais localizados em vertices
	int ans = INF;
	for(int i = 0; i < n; i++){
		int mx = 0;
		for(int j = 0; j < n; j++){
			if(dist[i][j] > mx)
				mx = dist[i][j];
		}
		if(mx < ans)
			ans = mx;
	}
	
	
	// Verificar se existe alguma resposta melhor nas arestas
	int emin[(N*(N-1))/2];
	#pragma omp parallel num_threads(N_THREADS)
	{
		#pragma omp for private(i)
		for(int i = 0; i < m; i++){
			int v1 = e[i][0];
			int v2 = e[i][1];
			int ew = e[i][2];

			// Obter os pontos de minimo na aresta e verificar se eh uma solucao
			// melhor do que a ja obtida
			emin[i] = INF;
			int minDist;
			for(int k = n-2, j = n-1; k >= 0; --k) {

				int gama = (dist[v2][indexes[v1][j]] - dist[v1][indexes[v1][k]] + ew)/2;
	            if((gama >= 0) && (gama <= ew)){
	                minDist = gama + dist[v1][indexes[v1][k]];

					if(minDist < emin[i]){
						emin[i] = minDist;	
					}
				}
				if(dist[v2][indexes[v1][j]] < dist[v2][indexes[v1][k]])
	                j = k;
			}	
		}	
	}

	// Verificar se algum minimo das arestas eh melhor que
	// o minimo dos vertices
	for(int i = 0; i < m; i++){
		if(emin[i]<ans)
			ans = emin[i];
	}

	cout << ((double)ans)/2 << endl;
	return 0;
}